﻿#include "configuration.h"

Configuration::Configuration(Translator &translotor)
  : _translator(translotor)
  {
  }

// @returns for each macro, returns Position of found _pattern. (-1,-1) if not found.
QMap<int, Position> Configuration::updateMacroRules(const Expression &exp)
  {
  // we can only generate the rules for _patterns that occur in the expression
  // BUT this means, that initially we can generate 0 rules
  // if then the exp changes, but LabelDictionaries stay the same (eg from a_i b_j => b_j a_i)
  // we will not generate rules if our pattern is b_%1 a_%2.

  // this means we have to update rules always. But this does not mean that performace will be much worse
  // actually, very often we won't generate any rules, because pattern won't be found.

  _generatedRules.clear();
  QMap<int, Position> positions;

  for (int i = 0; i < _macroRules.size(); i++)
    {
    QMap<int, Position>::iterator it = positions.insert(i, _macroRules[i].findPatternInExp(exp));
    if (it.value() != Position(-1, -1))
      {
      Expression subExpression;
      exp.subExpression(it.value().first, it.value().second, _macroRules[i].key().countWords(), subExpression);
      _generatedRules.append(_macroRules[i].generateRules(subExpression));
      }
    }
  return positions;
  qDebug() << "all right";
  }

bool Configuration::loadXML(QString path)
  {
  clear();

  if (path.isEmpty())
    path = "../nca.xml";
  QFile file(path);
  if (!file.open(QIODevice::ReadOnly))
    {
    qCritical() << "Failed to open config file: " << QDir::currentPath() << "/" << path;
    return false;
    }

  QXmlStreamReader xml;
  xml.addData(file.readAll());
  file.close();

  //A typical loop with QXmlStreamReader looks like this:
  QXmlStreamReader::TokenType tokenType = xml.tokenType();
  while (!xml.atEnd()) 
    {
    tokenType = xml.readNext();    
    if (tokenType == QXmlStreamReader::Characters)
      continue;
    if (tokenType == QXmlStreamReader::StartElement)
      {
      if (xml.name() == "treatLabelsAsNumbers") 
        _treatLabelsAsNumbers = parseData(xml).compare("true", Qt::CaseInsensitive);
      if (xml.name() == "rules")
        {
        if (!parseRules(xml))
          qCritical() << "Failed to parse rules.";
        }
      else if (xml.name() == "groups") 
        {
        if (!parseGroups(xml))
          qCritical() << "Failed to parse groups.";
        }
      else if (xml.name() == "registered_names") 
        {
        if (!parseRegisteredNames(xml))
          qCritical() << "Failed to parse registered names.";
        }
      else if (xml.name() == "labels_priorites")
        {
        if (!parseLabelsPriorities(xml))
          qCritical() << "Failed to parse labels' priorities.";
        }
      }
    }
  if (xml.hasError()) 
    {
    qCritical() << xml.error();
    return false;
    }
  if (_rules.isEmpty())
    {
    qCritical() << "No rules have been read.";
    return false;
    }
  return true;
  }

QString Configuration::parseData(QXmlStreamReader& xml)
  {
  xml.readNext();
  if(xml.tokenType() != QXmlStreamReader::Characters)
    {
    xmlWarning(xml, Q_FUNC_INFO + QString(" failed. Expected characters."));
    return "";
    }
  return xml.text().toString();
  }
  
bool Configuration::parseRules(QXmlStreamReader& xml)
  {
  while (!((xml.tokenType() == QXmlStreamReader::EndElement) && (xml.name() == "rules")))
    {
    if ((xml.tokenType() == QXmlStreamReader::StartElement) && (xml.name() == "rule"))
      {
      // parse one rule
      QXmlStreamAttributes attributes = xml.attributes();
      if (attributes.hasAttribute("pattern")) 
        {
        QString sPattern = attributes.value("pattern").toString();
        QString sReplacement = attributes.value("replacement").toString();
        Expression key, value;
        bool translationSuccess = _translator.stringToExp(sPattern, key);
        translationSuccess |= _translator.stringToExp(sReplacement, value);
        if (!translationSuccess)
          {
          if (key.size())
            qCritical() << QObject::tr("Failed to parse transformation value: %1. Ignoring.").arg(sReplacement);
          else
            qCritical() << QObject::tr("Failed to parse transformation key: %1. Ignoring.").arg(sPattern);
          xml.readNext();
          continue;
          }
        Rule rule(key, value);
        bool macro = false;
        // there is a problem here.
        // if I add MacroRule inside parseMacro, I might not still be done with Conditions.
        // TODO: enforce integrity of conditions. Changes in xml required :/ <conditions>
        while (!((xml.tokenType() == QXmlStreamReader::EndElement) && (xml.name() == "rule")))
          {
          if (xml.name() == "condition")
            {
            if (macro)
              qWarning() << "Conditions after <macro> tag will be ignored!";
            parseCondition(xml, rule);
            }
          if (xml.name() == "macro")
            {
            macro = true;
            parseMacro(xml, rule);
            }
          xml.readNext();
          }
        if (macro == false)
          _rules.push_back(rule);
        }
      else
        xmlWarning(xml, "Rule without a pattern.");
      }
    xml.readNext();
    }
  return true;
  }

bool Configuration::parseMacro(QXmlStreamReader& xml, Rule& rule)
  {
  if ((xml.tokenType() == QXmlStreamReader::StartElement) && (xml.name() == "macro"))
    {
//  <macro replacement="delta(%1.%i, %2.%i)" operation="sum">
    QXmlStreamAttributes attributes = xml.attributes();
    QString macroReplacement;
    MacroRule::MacroOperation operation = MacroRule::MO_PRODUCT;
    if (attributes.hasAttribute("replacement"))
      macroReplacement = attributes.value("replacement").toString();
    if (attributes.hasAttribute("operation"))
      {
      QString sOp = attributes.value("replacement").toString();
      if (sOp.contains("sum", Qt::CaseInsensitive))
        operation = MacroRule::MO_SUM;
      }
//  <var name="%i" min="0" max="%1.size - 1"/>
    QList<MacroVariable> vars;
    while (!(xml.name() == "macro" && xml.tokenType() == QXmlStreamReader::EndElement))
      {
      if (xml.name() == "var")
        {
        QXmlStreamAttributes attributes = xml.attributes();
        if (attributes.hasAttribute("name") && attributes.hasAttribute("min") && attributes.hasAttribute("max"))
          vars.append(MacroVariable(attributes.value("name").toString(),
                                    attributes.value("min").toString(),
                                    attributes.value("max").toString()));
        }
      xml.readNext();
      }
    _macroRules.append(MacroRule(rule, macroReplacement, operation, vars, _translator));
//  </macro>
    }
  return true;
  }

bool Configuration::parseCondition(QXmlStreamReader& xml, Rule &rule)
  {
  QStringList arguments;
  Condition::ConditionType ctType;
  bool invertOrder = false;

  while (!((xml.tokenType() == QXmlStreamReader::EndElement) && (xml.name() == "condition")))
    {
    if ((xml.tokenType() == QXmlStreamReader::StartElement) && (xml.name() == "condition"))
      {
      // parse one condition
      QXmlStreamAttributes attributes = xml.attributes();
      if (attributes.hasAttribute("type"))
        {
        QString sType = attributes.value("type").toString();
        QStringList types;
        types << ">" << ">=" << "=" << "<" << "<=" << "==" << "!=";
        switch (types.indexOf(sType))
          {
          case 0: // >
            ctType = Condition::CT_GREATER;
            break;
          case 1: // >=
            ctType = Condition::CT_GREATER_OR_EQUAL;
            break;
          case 5: // ==
          case 2: // =
            ctType = Condition::CT_EQUALS;
            break;
          case 3: // <
            ctType = Condition::CT_GREATER;
            invertOrder = true;
            break;
          case 4: // <=
            ctType = Condition::CT_GREATER_OR_EQUAL;
            invertOrder = true;
            break;
          case 6: // !=
            ctType = Condition::CT_NOT_EQUAL;
            break;
          }
        }
      }
    else if ((xml.tokenType() == QXmlStreamReader::StartElement) && (xml.name() == "arg"))
      arguments.append(parseData(xml));
    xml.readNext();
    }
  if (invertOrder)
    {
    if (arguments.size() != 2)
      return false;
    else
      arguments.swap(0, 1);
    }
  rule.addCondition(Condition(ctType, arguments));
  return true;
  }

bool Configuration::parseRegisteredNames(QXmlStreamReader& xml)
  {
  _translator.clearRegisteredNames();
  QXmlStreamReader::TokenType tokenType = xml.readNext();
  while (!((tokenType == QXmlStreamReader::EndElement) && (xml.name() == "registered_names")))
    {
    if (tokenType == QXmlStreamReader::StartElement)
      {
      QXmlStreamAttributes attributes = xml.attributes();
      QString sName;
      if (attributes.hasAttribute("name")) 
        sName = attributes.value("name").toString();
      else
        xmlWarning(xml, "Registered name without a name.");

      if (!sName.isEmpty())
        {
        if (xml.name() == "operator")
          _translator.registerName(sName, Word::WT_OPERATOR);
        else if (xml.name() == "delta")
          _translator.registerName(sName, Word::WT_DELTA);
        }
      }
    tokenType = xml.readNext();
    }
  return true;
  }

bool Configuration::parseGroups(QXmlStreamReader& xml)
  {
  QList <GroupLabel > groupList; // each group has own QList
  QList <QStringList> tmpRelationList;
  while (!((xml.tokenType() == QXmlStreamReader::EndElement) && (xml.name() == "groups")))
    {
    if ((xml.tokenType() == QXmlStreamReader::StartElement) && (xml.name() == "group"))
      {
      QStringList tmpRelationSubgroupList;
      QXmlStreamAttributes attributes = xml.attributes();
      GroupLabel currentGroup( attributes.value("name").toString() );
      tmpRelationSubgroupList.append(attributes.value("name").toString());

      xml.readNext();
      while ((xml.name() != "group")) // search inside whole group
        {
        attributes = xml.attributes();
        if (xml.name() == "label" && attributes.hasAttribute("name"))
          {
          currentGroup.addLabel(attributes.value("name").toString());
          }
        else if (xml.name() == "subgroup" && attributes.hasAttribute("name"))
          {
          tmpRelationSubgroupList.append(attributes.value("name").toString());
          }
        xml.readNext();
        }
      groupList.append(currentGroup); // in the beginning list contains only one groupLabel
      tmpRelationList.append(tmpRelationSubgroupList);
      }
    xml.readNext();
    }
   //move each GroupLabel from groupList into _labelsGrpah
   foreach(GroupLabel gl, groupList)
     {
     _gGraph.addGroup(gl);
     }
   //fill correlations inside _labelsGraph
   foreach(QStringList sl, tmpRelationList)
     {
     for (int i = 1; i < sl.size(); i++)
       _gGraph.addSubgroup(sl.first(), sl.at(i));
     }

  return true;
  }

bool Configuration::parseLabelsPriorities(QXmlStreamReader& xml)
  {
  QXmlStreamReader::TokenType tokenType = xml.readNext();
  while (!((tokenType == QXmlStreamReader::EndElement) && (xml.name() == "labels_priorites")))
    {
    if (tokenType == QXmlStreamReader::StartElement)
      {
      QXmlStreamAttributes attributes = xml.attributes();
      QString sName;
      if (attributes.hasAttribute("name"))
        sName = attributes.value("name").toString();
      else
        xmlWarning(xml, "Label without a name.");

      if (!sName.isEmpty())
        {
        _labelsPriorities.append(sName);
        }
      }
    tokenType = xml.readNext();
    }
  //qWarning() << _labelsPriorities;
  return true;
  }

void Configuration::clear()
  {
  _rules.clear();
  _gGraph.clear();
  _labelsPriorities.clear();
  _generatedRules.clear();
  _macroRules.clear();
  _treatLabelsAsNumbers = false;
  }

void Configuration::xmlWarning(QXmlStreamReader& xml, QString msg)
  {
  msg +=  " TokenType: " + xml.tokenString() + " ";
  if (!xml.text().isNull())
    msg += xml.text();
  if (!xml.name().isNull())
    msg += xml.name();

  qWarning() << msg ;
  }
